package com.sromku.simple.fb;
import java.lang.reflect.Field;
import java.util.List;
import android.app.Activity;
import android.content.Intent;
import com.facebook.FacebookOperationCanceledException;
import com.facebook.Session;
import com.facebook.Session.AuthorizationRequest;
import com.facebook.SessionState;
import com.facebook.UiLifecycleHelper;
import com.facebook.widget.FacebookDialog;
import com.facebook.widget.FacebookDialog.Callback;
import com.facebook.widget.FacebookDialog.PendingCall;
import com.sromku.simple.fb.Permission.Type;
import com.sromku.simple.fb.SimpleFacebookConfiguration.Builder;
import com.sromku.simple.fb.listeners.OnLoginListener;
import com.sromku.simple.fb.listeners.OnLogoutListener;
import com.sromku.simple.fb.listeners.OnNewPermissionsListener;
import com.sromku.simple.fb.listeners.OnReopenSessionListener;
import com.sromku.simple.fb.utils.Logger;
public class SessionManager {
private final static Class<?> TAG = SessionManager.class;
static Activity activity;
static SimpleFacebookConfiguration configuration;
private final SessionStatusCallback mSessionStatusCallback;
private UiLifecycleHelper uiLifecycleHelper;
private Callback mFacebookDialogCallback;
public SessionManager(Activity activity, SimpleFacebookConfiguration configuration) {
SessionManager.activity = activity;
SessionManager.configuration = configuration;
mSessionStatusCallback = new SessionStatusCallback();
uiLifecycleHelper = new UiLifecycleHelper(activity, mSessionStatusCallback);
}
/**
* Login to Facebook
*
* @param onLoginListener
*/
public void login(OnLoginListener onLoginListener) {
if (onLoginListener == null) {
Logger.logError(TAG, "OnLoginListener can't be null in -> 'login(OnLoginListener onLoginListener)' method.");
return;
}
if (activity == null) {
onLoginListener.onFail("You must initialize the SimpleFacebook instance with you current Activity.");
return;
}
if (isLogin(true)) {
Logger.logInfo(TAG, "You were already logged in before calling 'login()' method.");
onLoginListener.onLogin();
return;
}
Session session = getOrCreateActiveSession();
if (hasPendingRequest(session)) {
Logger.logWarning(TAG, "You are trying to login one more time, before finishing the previous login call");
return;
}
mSessionStatusCallback.onLoginListener = onLoginListener;
session.addCallback(mSessionStatusCallback);
if (!session.isOpened()) {
openSession(session, true);
}
else {
onLoginListener.onLogin();
}
}
/**
* Logout from Facebook
*/
public void logout(OnLogoutListener onLogoutListener) {
if (onLogoutListener == null) {
Logger.logError(TAG, "OnLogoutListener can't be null in -> 'logout(OnLogoutListener onLogoutListener)' method");
return;
}
Session session = getActiveSession();
if (session != null) {
if (session.isClosed()) {
Logger.logInfo(SessionManager.class, "You were already logged out before calling 'logout()' method");
onLogoutListener.onLogout();
}
else {
mSessionStatusCallback.onLogoutListener = onLogoutListener;
session.closeAndClearTokenInformation();
session.removeCallback(mSessionStatusCallback);
onLogoutListener.onLogout();
}
}
else {
onLogoutListener.onLogout();
}
}
/**
* Indicate if you are logged in or not.
*
* @return <code>True</code> if you is logged in, otherwise return
* <code>False</code>
*/
public boolean isLogin(boolean reopenIfPossible) {
Session session = getActiveSession();
if (session == null) {
if (activity == null) {
return false;
}
session = new Session.Builder(activity.getApplicationContext()).setApplicationId(configuration.getAppId()).build();
Session.setActiveSession(session);
}
if (session.isOpened()) {
return true;
}
if (reopenIfPossible && canReopenSession(session)) {
reopenSession();
return true;
}
return false;
}
/**
* Get the current 'Active' session. <br>
* <br>
* <b>Important:</b> The result could be <code>null</code>. If you want to
* have not null active session, then use
* {@link #getOrCreateActiveSession()} method.
*
* @return Active session or null.
*/
public Session getActiveSession() {
return Session.getActiveSession();
}
/**
* Get access token of open session
*
* @return a {@link String} containing the Access Token of the current
* {@link Session} or null if no session.
*/
public String getAccessToken() {
Session session = getActiveSession();
if (session != null) {
return session.getAccessToken();
}
return null;
}
public SessionStatusCallback getSessionStatusCallback() {
return mSessionStatusCallback;
}
/**
* Get permissions that are accepted by user for current token
*
* @return the list of accepted permissions
*/
public List<String> getActiveSessionPermissions() {
return getActiveSession().getPermissions();
}
public Activity getActivity() {
return activity;
}
/**
* Return true if there is no pending request like: asking for permissions..
*
* @return
*/
public boolean canMakeAdditionalRequest() {
Session session = Session.getActiveSession();
if (session != null) {
return !hasPendingRequest(session);
}
return true;
}
/**
* Return true if current session contains all publish permissions.
*
* @return
*/
public boolean containsAllPublishPermissions() {
if (getActiveSessionPermissions().containsAll(configuration.getPublishPermissions())) {
return true;
}
return false;
}
/**
* Extend and ask user for PUBLISH permissions
*
* @param activity
*/
public void extendPublishPermissions() {
Session session = Session.getActiveSession();
if (hasPendingRequest(session)) {
Logger.logWarning(TAG, "You are trying to ask for publish permission one more time, before finishing the previous login call");
}
Session.NewPermissionsRequest request = new Session.NewPermissionsRequest(activity, configuration.getPublishPermissions());
session.addCallback(mSessionStatusCallback);
session.requestNewPublishPermissions(request);
}
public void openSession(Session session, boolean isRead) {
Session.OpenRequest request = new Session.OpenRequest(activity);
if (request != null) {
request.setDefaultAudience(configuration.getSessionDefaultAudience());
request.setLoginBehavior(configuration.getSessionLoginBehavior());
if (isRead) {
request.setPermissions(configuration.getReadPermissions());
/*
* In case there are also PUBLISH permissions, then we would ask
* for these permissions second time (after, user accepted the
* read permissions)
*/
if (configuration.hasPublishPermissions() && configuration.isAllPermissionsAtOnce()) {
mSessionStatusCallback.setAskPublishPermissions(true);
}
// Open session with read permissions
session.openForRead(request);
}
else {
request.setPermissions(configuration.getPublishPermissions());
session.openForPublish(request);
}
}
}
/**
* Requests any new permission in a runtime. <br>
* <br>
*
* Useful when you just want to request the action and won't be publishing
* at the time, but still need the updated <b>access token</b> with the
* permissions (possibly to pass back to your backend).
*
* <br>
* <b>Must be logged to use.</b>
*
* @param permissions
* New permissions you want to have. This array can include READ
* and PUBLISH permissions in the same time. Just ask what you
* need.<br>
* <br>
* @param showPublish
* This flag is relevant only in cases when new permissions
* include PUBLISH permission. Then you can decide if you want
* the dialog of requesting publish permission to appear <b>right
* away</b> or <b>later</b>, at first time of real publish
* action.<br>
* <br>
* The configuration of
* {@link Builder#setAskForAllPermissionsAtOnce(boolean)} will
* not take effect for this method, because you define here by
* setting <code>showPublish</code> what would you like to see. <br><br>
* @param onNewPermissionListener
* The callback listener for the requesting new permission
* action.
*/
public void requestNewPermissions(Permission[] permissions, final boolean showPublish, final OnNewPermissionsListener onNewPermissionListener) {
configuration.addNewPermissions(permissions);
logout(new OnLogoutAdapter() {
@Override
public void onLogout() {
final boolean prevValue = configuration.mAllAtOnce;
configuration.mAllAtOnce = showPublish;
login(new OnLoginListener() {
@Override
public void onFail(String reason) {
configuration.mAllAtOnce = prevValue;
onNewPermissionListener.onFail(reason);
}
@Override
public void onException(Throwable throwable) {
configuration.mAllAtOnce = prevValue;
onNewPermissionListener.onException(throwable);
}
@Override
public void onThinking() {
configuration.mAllAtOnce = prevValue;
onNewPermissionListener.onThinking();
}
@Override
public void onNotAcceptingPermissions(Type type) {
configuration.mAllAtOnce = prevValue;
onNewPermissionListener.onNotAcceptingPermissions(type);
}
@Override
public void onLogin() {
configuration.mAllAtOnce = prevValue;
onNewPermissionListener.onSuccess(getAccessToken());
}
});
}
});
}
/**
* Call this method only if session really needs to be reopened for read or
* for publish. <br>
* <br>
*
* <b>Important:</b><br>
* Any open method must be called at most once, and cannot be called after
* the Session is closed. Calling the method at an invalid time will result
* in {@link UnsupportedOperationException}.
*/
public void reopenSession() {
Session session = Session.getActiveSession();
if (session != null && session.getState().equals(SessionState.CREATED_TOKEN_LOADED)) {
List<String> permissions = session.getPermissions();
List<String> publishPermissions = configuration.getPublishPermissions();
if (publishPermissions != null && publishPermissions.size() > 0 && permissions.containsAll(publishPermissions)) {
openSession(session, false);
}
else if (permissions.containsAll(configuration.getReadPermissions())) {
openSession(session, true);
}
}
}
public void trackFacebookDialogPendingCall(PendingCall pendingCall, FacebookDialog.Callback callback) {
mFacebookDialogCallback = callback;
uiLifecycleHelper.trackPendingDialogCall(pendingCall);
}
public void untrackPendingCall() {
mFacebookDialogCallback = null;
}
public boolean onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
uiLifecycleHelper.onActivityResult(requestCode, resultCode, data, mFacebookDialogCallback);
return true;
}
/**
* Check if we already have Active session. If such exists, then return it,
* otherwise create and set new Active session. <br>
* <br>
*
* <b>Note:</b><br>
* <li>'Active' session doesn't meant that it's ready for running requests
* on facebook side. For being able to run requests we need this session to
* be 'Open'.</li> <li>You can't create a session if the activity/context
* hasn't been initialized This is now possible because the library can be
* started without context.</li><br>
*
* @return Active session or <code>null</code> if activity wasn't
* initialized in SimpleFacebook class.
*/
private Session getOrCreateActiveSession() {
if (activity == null) {
Logger.logError(TAG, "You must initialize the SimpleFacebook instance with you current Activity.");
return null;
}
if (getActiveSession() == null || getActiveSession().isClosed()) {
Session session = new Session.Builder(activity.getApplicationContext()).setApplicationId(configuration.getAppId()).build();
Session.setActiveSession(session);
}
return getActiveSession();
}
/**
* @param session
* @return <code>True</code> if is possible to relive and reopen the current
* active session. Otherwise return <code>False</code>.
*/
private boolean canReopenSession(Session session) {
if (activity == null) {
Logger.logError(TAG, "You must initialize the SimpleFacebook instance with you current Activity.");
return false;
}
if (SessionState.CREATED_TOKEN_LOADED.equals(session.getState())) {
List<String> permissions = getActiveSessionPermissions();
if (permissions.containsAll(configuration.getReadPermissions())) {
return true;
}
}
return false;
}
private boolean hasPendingRequest(Session session) {
try {
Field f = session.getClass().getDeclaredField("pendingAuthorizationRequest");
f.setAccessible(true);
AuthorizationRequest authorizationRequest = (AuthorizationRequest) f.get(session);
if (authorizationRequest != null) {
return true;
}
}
catch (Exception e) {
// do nothing
}
return false;
}
public class SessionStatusCallback implements Session.StatusCallback {
private boolean askPublishPermissions = false;
private boolean doOnLogin = false;
private OnReopenSessionListener onReopenSessionListener = null;
OnLoginListener onLoginListener = null;
OnLogoutListener onLogoutListener = null;
public void setOnReopenSessionListener(OnReopenSessionListener onReopenSessionListener) {
this.onReopenSessionListener = onReopenSessionListener;
}
@Override
public void call(Session session, SessionState state, Exception exception) {
List<String> permissions = getActiveSessionPermissions();
if (exception != null) {
if (exception instanceof FacebookOperationCanceledException && !SessionState.OPENED_TOKEN_UPDATED.equals(state)) {
if (permissions.size() == 0) {
notAcceptedPermission(Permission.Type.READ);
}
else {
notAcceptedPermission(Permission.Type.PUBLISH);
}
}
else {
if (onLoginListener != null) {
onLoginListener.onException(exception);
}
}
}
switch (state) {
case CLOSED:
if (onLogoutListener != null) {
onLogoutListener.onLogout();
}
break;
case CLOSED_LOGIN_FAILED:
break;
case CREATED:
break;
case CREATED_TOKEN_LOADED:
break;
case OPENING:
if (onLoginListener != null) {
onLoginListener.onThinking();
}
break;
case OPENED:
/*
* Check if we came from publishing actions where we ask again
* for publish permissions
*/
if (onReopenSessionListener != null) {
onReopenSessionListener.onNotAcceptingPermissions(Permission.Type.PUBLISH);
onReopenSessionListener = null;
}
/*
* Check if WRITE permissions were also defined in the
* configuration. If so, then ask in another dialog for WRITE
* permissions.
*/
else if (askPublishPermissions && session.getState().equals(SessionState.OPENED)) {
if (doOnLogin) {
/*
* If user didn't accept the publish permissions, we
* still want to notify about complete
*/
doOnLogin = false;
onLoginListener.onLogin();
}
else {
doOnLogin = true;
extendPublishPermissions();
askPublishPermissions = false;
}
}
else {
if (onLoginListener != null) {
onLoginListener.onLogin();
}
}
break;
case OPENED_TOKEN_UPDATED:
/*
* Check if came from publishing actions and we need to re-ask
* for publish permissions
*/
if (onReopenSessionListener != null) {
if ((exception != null && exception instanceof FacebookOperationCanceledException) || (!containsAllPublishPermissions())) {
onReopenSessionListener.onNotAcceptingPermissions(Permission.Type.PUBLISH);
}
else {
onReopenSessionListener.onSuccess();
}
onReopenSessionListener = null;
}
else if (doOnLogin) {
doOnLogin = false;
if (onLoginListener != null) {
onLoginListener.onLogin();
}
}
break;
default:
break;
}
}
/**
* If we want to open another dialog with publish permissions just after
* showing read permissions, then this method should be called
*/
public void setAskPublishPermissions(boolean ask) {
askPublishPermissions = ask;
}
private void notAcceptedPermission(Permission.Type type) {
if (onLoginListener != null) {
onLoginListener.onNotAcceptingPermissions(type);
}
}
}
private class OnLogoutAdapter implements OnLogoutListener {
@Override
public void onThinking() {
}
@Override
public void onException(Throwable throwable) {
}
@Override
public void onFail(String reason) {
}
@Override
public void onLogout() {
}
}
}